#include "StdAfx.h"
#include "Player.h"

//===========================================================================
// Player
//===========================================================================

//===========================================================================
Player::Player(
    EPieceType piece,
    IBoard *   board
)   : m_piece( piece )
    , m_board( board )
{
    assert( this->m_board );
}

//===========================================================================
bool Player::Move( void )
{
    Position position;
    ECourse  course;
    
    const char * message = NULL;
    while( true )
    {
        if( message ) {
            std::cout << message << std::endl;
            message = NULL;
        }

        bool abandon = false;
        if( !this->GetUserMoveCommand( position, course, abandon ) ) {
            if( abandon )
                return false;
            
            message = "invalid input";
            continue;
        }

        if( !this->Move( position, course ) ) {
            message = "invalid move";
            continue;
        }
            
        break;
    }
    
    return true;
}

//===========================================================================
bool Player::GetUserCourseCommand(
    ECourse & course
) {
    const char * message = "input your course to jump ( [L|R] ) : ";
    std::cout << message;

    std::string input;
    std::getline( std::cin, input );

    if( !this->StringToCourse( input.c_str(), course ) )
        return false;

    return true;
}

//===========================================================================
bool Player::StringToCourse(
    const char *    str,
            ECourse & course
) {
    if( !str || !*str )
        return false;
    
    switch( *str )
    {
        case 'l' : case 'L' : course = EC_LEFT;  break;
        case 'r' : case 'R' : course = EC_RIGHT; break;
        default : return false;
    }

    return true;
}

//===========================================================================
bool Player::GetUserMoveCommand(
    Position & position,
    ECourse &  course,
    bool &     abandon
) {
    abandon = false;
    
    const char * message = "input your move ( x,y,[L|R] ) : ";
    std::cout << message;

    std::string input;
    std::getline( std::cin, input );
    const char * giveup = "give up";
    if( !strncmp(
            input.c_str(),
            giveup,
            strlen( giveup )
    ) ) {
        abandon = true;
        return false;
    }
    
    std::stringstream stream( input );
    std::vector< std::string > commands;
    std::string val;
    while( std::getline( stream, val, ',' ) )
        commands.push_back( val );

    if( 3 != commands.size() )
        return false;
    
    if( !this->StringToCourse( commands[ 2 ].c_str(), course ) )
        return false;
    
    position.Set(
        std::atoi( commands[ 0 ].c_str() ),
        std::atoi( commands[ 1 ].c_str() )
    );

    return true;
}

//===========================================================================
ECourse Player::CanJumpAgain(
    IPosition & position
) {
    ECourse available = EC_NONE;
    if( this->CanJumpAgain( position, EC_LEFT ) )
        available = ECourse( available | EC_LEFT );    
    if( this->CanJumpAgain( position, EC_RIGHT ) )
        available = ECourse( available | EC_RIGHT );
    
    return available;
}

//===========================================================================
bool Player::CanJumpAgain(
    IPosition & position,
    ECourse     course
) {
    Position middle;
    const EPieceType * piece = this->GetDest(
        position,
        course,
        middle
    );
    if(    !piece
        || *piece == this->m_piece
        || *piece == EPT_NONE )
        return false;

    Position dest;
    piece = this->GetDest(
        middle,
        course,
        dest
    );
    if( !piece || *piece != EPT_NONE )
        return false;

    return true;
}

//===========================================================================
bool Player::GetDestPosition(
    const IPosition & position,
          ECourse     course,
          Position &  dest
) {
    unsigned int y = position.GetY();
    switch( course )
    {
        case EC_LEFT  : y -= 1; break;
        case EC_RIGHT : y += 1; break;
        default : return false;
    }
    unsigned int x = position.GetX() - 1;

    dest.Set( x, y );
    return true;
}

//===========================================================================
bool Player::Move(
    IPosition & position,
    ECourse     course
) {
    const EPieceType * piece = this->m_board->GetPiece( position );
    if( !piece || this->m_piece != *piece )
        return false;
    
    if( this->m_piece != *piece )
        return false;

    Position dest;
    const EPieceType * destPiece = this->GetDest(
        position,
        course,
        dest
    );
    if( !destPiece || this->m_piece == *destPiece )
        return false;

    if( EPT_NONE == *destPiece ) {
        this->m_board->Set( position, EPT_NONE );
        this->m_board->Set( dest,     this->m_piece );

        return true;
    }

    return this->TryToJump(
        position,
        course
    );
}

//===========================================================================
bool Player::TryToJump(
    IPosition & position,
    ECourse     course
) {
    Position middle;
    const EPieceType * piece = this->GetDest(
        position,
        course,
        middle
    );
    if(    !piece
        || *piece == this->m_piece
        || *piece == EPT_NONE )
        return false;

    Position dest;
    piece = this->GetDest(
        middle,
        course,
        dest
    );
    if( !piece || *piece != EPT_NONE )
        return false;

    this->m_board->Set( position, EPT_NONE );
    this->m_board->Take( middle );
    this->m_board->Set( dest, this->m_piece );

    ECourse available = this->CanJumpAgain( dest );
    if( EC_LEFT & available && EC_RIGHT & available ) {
        while( !this->GetUserCourseCommand( course ) ) {
        }
        return this->TryToJump( dest, course );
    }
    else if( EC_LEFT & available )
        return this->TryToJump( dest, EC_LEFT );
    else if( EC_RIGHT & available )
        return this->TryToJump( dest, EC_RIGHT );
    
    return true;
}

//===========================================================================
const EPieceType * Player::GetDest(
    IPosition & position,
    ECourse     course,
    Position &  dest
) {
    if( !this->GetDestPosition( position, course, dest ) )
        return NULL;

    return this->m_board->GetPiece( dest );
}

//===========================================================================
void Player::Release( void )
{
    Player::Release( this );
}

//===========================================================================
void Player::Release( Player * player )
{
    if( player )
        delete player;
}

//===========================================================================
IPlayer * CreatePlayer(
    EPieceType piece,
    IBoard *   board
) {
    return new Player(
        piece,
        board
    );
}

//===========================================================================
// PlayerAI
//===========================================================================

//===========================================================================
PlayerAI::PlayerAI(
    EPieceType piece,
    IBoard *   board
)   : Player( piece, board )
{
    srand( static_cast< unsigned int >( time( NULL ) ) );
}

//===========================================================================
bool PlayerAI::Move( void )
{
    Routes routes;
    this->FindBestRoutes( routes );
    unsigned int size = routes.size();
    if( !size )
        return true;
    
    unsigned int index = std::rand() % size;
    bool result =  this->Move( routes[ index ] );
    routes.clear();

    return result;
}

//===========================================================================
bool PlayerAI::Move(
    Route & route
) {
    if(    !route.GetLeftNode()
        && !route.GetRightNode() )
        return false;
    
    const Route * node = &route;
    while( node )
    {
        const Route * dest = this->FindDeepestNode( node );
        if( !dest )
            break;

        this->Move(
            node->GetPosition(),
            dest->GetPosition()
        );
        node = dest;
    }
    
    return true;
}

//===========================================================================
const Route * PlayerAI::FindDeepestNode(
    const Route * route
) {
    const Route * left  = route->GetLeftNode();
    const Route * right = route->GetRightNode();
    unsigned int x = route->GetPosition().GetX();

    if( left && right )
    {
        unsigned int depthL = left->GetDepth();
        unsigned int depthR = right->GetDepth();
        if( depthL == depthR ) {
            unsigned int diffL = left->GetPosition().GetX() - x;
            unsigned int diffR = right->GetPosition().GetX() - x;
            if( diffL == diffR ) {
                const Route * routes[ 2 ] = {
                    left, right
                };
                unsigned int index = std::rand() % 2;
                return routes[ index ];
            }
            else if( diffL > diffR )
                return left;
            else
                return right;
        }
        else if( depthL > depthR )
            return left;
        else
            return right;
    }
    else if( left )
        return left;
    else if( right )
        return right;
    
    return NULL;
}

//===========================================================================
bool PlayerAI::Move(
    const IPosition & from,
    const IPosition & to
) {
    this->m_board->Set( from, EPT_NONE );
    this->m_board->Set( to, this->m_piece );

    if( 2 == Position::GetRowDiff( from, to ) ) {
        unsigned int x = to.GetX() - 1;
        unsigned int y = from.GetY() > to.GetY()
                       ? from.GetY() - 1
                       : to.GetY() - 1;

        this->m_board->Take( Position( x, y ) );
    }
    
    return true;
}

//===========================================================================
void PlayerAI::FindBestRoutes(
    Routes & routes
) {
    unsigned int maxDepth = 0;
    for( unsigned int i = 0; i < this->m_board->GetSize(); i++ ) {
        for( unsigned int j = 0; j < this->m_board->GetSize(); j++ ) {
            
            Position position( i, j );
            const EPieceType * piece = this->m_board->GetPiece( position );
            if( piece && this->m_piece == *piece ) {
                Route route( NULL, this->m_board, this->m_piece, position );
                unsigned int depth = route.SearchAllRoutes();
                if( !depth || depth < maxDepth )
                    continue;

                if( depth > maxDepth ) {
                    routes.clear();
                    maxDepth = depth;
                }
                routes.push_back( route );
            }
        }
    }
}

//===========================================================================
void PlayerAI::Release( void )
{
    PlayerAI::Release( this );
}

//===========================================================================
void PlayerAI::Release( PlayerAI * player )
{
    if( player )
        delete player;
}

//===========================================================================
IPlayer * CreatePlayerAI(
    EPieceType piece,
    IBoard *   board
) {
    return new PlayerAI(
        piece,
        board
    );
}